from typing import Optional, Tuple
import aiohttp
import logging
import secrets
from pydantic import BaseModel
from aiohttp import ClientTimeout
from fastapi import HTTPException

from app.core.config import settings
from app.core.constants import BoolEnum
from app.models.auth_account_model import AuthAccountManager, AuthProviderEnum, UserAuthAccountModel
from app.models.user_model import UserModel

# Configure logging
logger = logging.getLogger(__name__)


class GithubAuthError(Exception):
    """GitHub认证相关错误"""

    pass


class GithubUser(BaseModel):
    """GitHub用户信息"""

    id: int
    login: str
    name: Optional[str] = None
    email: Optional[str] = None
    avatar_url: Optional[str] = None


class GithubAuthService:
    """GitHub OAuth认证服务"""

    # 设置超时时间
    TIMEOUT = ClientTimeout(total=10)

    # 所需权限范围
    REQUIRED_SCOPES = {"read:user", "user:email"}

    def __init__(self):
        self.client_id = settings.GITHUB_CLIENT_ID
        self.client_secret = settings.GITHUB_CLIENT_SECRET
        self.callback_url = settings.GITHUB_CALLBACK_URL

    def get_authorize_url(self) -> Tuple[str, str]:
        """
        获取GitHub授权URL

        Returns:
            Tuple[str, str]: 授权URL和state参数
        """
        # 生成state参数防止CSRF攻击
        state = secrets.token_urlsafe(32)

        params = {
            "client_id": self.client_id,
            "redirect_uri": self.callback_url,
            "scope": " ".join(self.REQUIRED_SCOPES),
            "allow_signup": "true",
            "state": state,
        }
        query = "&".join(f"{k}={v}" for k, v in params.items())
        return f"https://github.com/login/oauth/authorize?{query}", state

    async def get_access_token(self, code: str, state: str, expected_state: str) -> str:
        """
        获取GitHub访问令牌

        Args:
            code: 授权码
            state: 收到的state参数
            expected_state: 预期的state参数

        Returns:
            str: 访问令牌

        Raises:
            GithubAuthError: 认证失败
        """
        # 验证state参数
        if state != expected_state:
            raise GithubAuthError("Invalid state parameter")

        try:
            async with aiohttp.ClientSession(timeout=self.TIMEOUT) as session:
                async with session.post(
                    "https://github.com/login/oauth/access_token",
                    json={
                        "client_id": self.client_id,
                        "client_secret": self.client_secret,
                        "code": code,
                    },
                    headers={"Accept": "application/json"},
                ) as resp:
                    if resp.status != 200:
                        error_body = await resp.text()
                        logger.error(f"GitHub access token request failed: {error_body}")
                        raise GithubAuthError("Failed to get access token")

                    data = await resp.json()
                    access_token = data.get("access_token")
                    if not access_token:
                        raise GithubAuthError("No access token in response")

                    # 验证权限范围
                    scopes = set(data.get("scope", "").split(","))
                    if not self.REQUIRED_SCOPES.issubset(scopes):
                        raise GithubAuthError("Insufficient permissions")

                    return access_token

        except aiohttp.ClientError as e:
            logger.error(f"Network error during token request: {str(e)}")
            raise GithubAuthError("Network error") from e

    async def get_user_info(self, access_token: str) -> GithubUser:
        """
        获取GitHub用户信息

        Args:
            access_token: 访问令牌

        Returns:
            GithubUser: 用户信息

        Raises:
            GithubAuthError: 获取用户信息失败
        """
        try:
            async with aiohttp.ClientSession(timeout=self.TIMEOUT) as session:
                # 获取基本信息
                async with session.get(
                    "https://api.github.com/user",
                    headers={
                        "Authorization": f"token {access_token}",
                        "Accept": "application/json",
                    },
                ) as resp:
                    if resp.status != 200:
                        error_body = await resp.text()
                        logger.error(f"GitHub user info request failed: {error_body}")
                        raise GithubAuthError("Failed to get user info")

                    user_data = await resp.json()

                # 如果没有email,尝试获取邮箱列表
                if not user_data.get("email"):
                    async with session.get(
                        "https://api.github.com/user/emails",
                        headers={
                            "Authorization": f"token {access_token}",
                            "Accept": "application/json",
                        },
                    ) as resp:
                        if resp.status == 200:
                            emails = await resp.json()
                            # 获取主邮箱
                            primary_email = next((email["email"] for email in emails if email["primary"]), None)
                            if primary_email:
                                user_data["email"] = primary_email

                try:
                    return GithubUser(**user_data)
                except ValueError as e:
                    logger.error(f"Invalid user data from GitHub: {str(e)}")
                    raise GithubAuthError("Invalid user data") from e

        except aiohttp.ClientError as e:
            logger.error(f"Network error during user info request: {str(e)}")
            raise GithubAuthError("Network error") from e

    async def get_or_create_user(self, github_user: GithubUser) -> Tuple[UserModel, UserAuthAccountModel]:
        """
        获取或创建用户

        Args:
            github_user: GitHub用户信息

        Returns:
            Tuple[UserModel, UserAuthAccountModel]: 用户模型和认证账号

        Raises:
            HTTPException: 用户创建或更新失败
        """
        try:
            # 查找GitHub账号
            auth_account = await UserAuthAccountModel.get_by_identifier(
                provider=AuthProviderEnum.GITHUB, identifier=str(github_user.id)
            )

            if auth_account:
                # 检查用户状态
                if not auth_account.user.is_active:
                    raise HTTPException(status_code=403, detail="User account is deactivated")

                # 更新GitHub账号信息
                auth_account.metadata = {
                    "username": github_user.login,
                    "name": github_user.name,
                    "email": github_user.email,
                    "avatar_url": github_user.avatar_url,
                }
                await auth_account.save()
                return auth_account.user, auth_account

            # 创建新用户和GitHub账号
            user = await UserModel.create(
                username=f"github_{github_user.login}",
                is_active=BoolEnum.TRUE.value,
            )

            # 创建GitHub账号
            auth_account = await AuthAccountManager.bind_account(
                user=user,
                provider=AuthProviderEnum.GITHUB,
                identifier=str(github_user.id),
                metadata={
                    "username": github_user.login,
                    "name": github_user.name,
                    "email": github_user.email,
                    "avatar_url": github_user.avatar_url,
                },
                is_verified=True,
                is_primary=True,
            )

            # 如果有邮箱,创建邮箱账号
            if github_user.email:
                await AuthAccountManager.bind_account(
                    user=user,
                    provider=AuthProviderEnum.EMAIL,
                    identifier=github_user.email,
                    is_verified=True,
                    is_primary=True,
                )

            # 创建用户资料
            await user.profile.create(nickname=github_user.name or github_user.login, avatar=github_user.avatar_url)

            return user, auth_account

        except Exception as e:
            logger.error(f"Error in get_or_create_user: {str(e)}")
            raise HTTPException(status_code=500, detail="Failed to process user data")


"""
使用示例:

from fastapi import Depends, HTTPException
from app.services.github_auth_service import GithubAuthService, GithubAuthError

@router.get("/github/login")
async def github_login():
    auth_service = GithubAuthService()
    authorize_url, state = auth_service.get_authorize_url()
    # 保存state到session
    session['github_oauth_state'] = state
    return {"authorize_url": authorize_url}

@router.get("/github/callback")
async def github_callback(
    code: str,
    state: str,
    auth_service: GithubAuthService = Depends()
):
    try:
        # 验证state
        expected_state = session.pop('github_oauth_state', None)
        if not expected_state:
            raise HTTPException(status_code=400, detail="Invalid session")
            
        # 获取访问令牌
        access_token = await auth_service.get_access_token(code, state, expected_state)
        
        # 获取用户信息
        github_user = await auth_service.get_user_info(access_token)
        
        # 获取或创建用户
        user, auth_account = await auth_service.get_or_create_user(github_user)
        
        # 处理登录...
        
    except GithubAuthError as e:
        raise HTTPException(status_code=400, detail=str(e))
"""
